# vPresent is an immersive presentation creation and display application.
# Copyright (C) 2007-2009 by Priority 5 Holdings, Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import os
import types

from PyQt4 import QtCore, QtGui

import spherebackgroundui
import gui.common.imagerefedit

import gui.util.environment as environ
import opendocument

class ButtonFilter(QtCore.QObject):
   def __init__(self, sphereBack, parent=None):
      QtCore.QObject.__init__(self, parent)

      self.mSphereBackground = sphereBack

   def eventFilter(self, watched, event):
      if QtCore.QEvent.Enter == event.type():
         # Set the preview to the correct button.
         if self.mSphereBackground.mCurrentDialog is None:
            self.mSphereBackground.onPreviewCamera(watched)
      elif QtCore.QEvent.Leave == event.type():
         # If the mouse is leaving a button and a dialog is not
         # currently displayed clear the preview.
         if self.mSphereBackground.mCurrentDialog is None:
            self.mSphereBackground.onPreviewCamera(None)
      return QtCore.QObject.eventFilter(self, watched, event) 

class SphereBackground(QtGui.QWidget, spherebackgroundui.Ui_SphereBackgroundBase):
   def __init__(self, curDocument, undoStack, sphereBackground, mercator, parent=None):
      QtGui.QWidget.__init__(self, parent)
      self.curDocument = curDocument
      self.undoStack = undoStack
      self.mBackground = sphereBackground
      self.mImageRefs = opendocument.NodeFilter(self.mBackground, 'vpresent:fill-image-ref')
      self.mMercator = mercator

      self.setupUi()

      if self.mMercator:
         for b in self.mBtns:
            if b is not self.mLeftBtn:
               b.setVisible(False)
         self.mLeftBtn.setText("Mercator Image")

      self.__updateButtonIcons()

      # Create a button filter that listens for mouse enter events.
      self.mButtonFilter = ButtonFilter(self)
      for b in self.mBtns:
         b.installEventFilter(self.mButtonFilter)

      # Keep track of any saved camera state when we are previewing a cubemap.
      self.mSavedCamera = None
      # Keep track of the currently displayed dialog.
      self.mCurrentDialog = None
      # Store the undo index before opening dialog.
      self.mUndoIndex = 0

   def setupUi(self):
      spherebackgroundui.Ui_SphereBackgroundBase.setupUi(self, self)

      # Order from opendocument.vpresent: negx, posx, negy, posy, negz, posz.
      self.mBtns = [self.mLeftBtn, self.mRightBtn, self.mBottomBtn,
                    self.mTopBtn, self.mFrontBtn, self.mBackBtn]

      for (i,b) in enumerate(self.mBtns):
         self.connect(b, QtCore.SIGNAL("toggled(bool)"), lambda c,idx=i:self.onToggled(idx,c))
         self.connect(b, QtCore.SIGNAL("pressed()"), self.onPressed)

   def __updateButtonIcons(self):
      """
      Called when a image reference has changed and we need to update the
      tool button icons.
      """
      for (idx,b) in enumerate(self.mBtns):
         if idx < len(self.mImageRefs):
            img_ref = self.mImageRefs[idx]
            fill_image = self.curDocument.document.getFillImage(img_ref.draw_fill_image_name)
            if fill_image is not None:
               thumbnail = fill_image._getThumbnail()
               if thumbnail is not None:
                  b.setIcon(thumbnail)

   def __onRefChanged(self, imageRef):
      """ Called when a image reference is changed. """
      # Update the button icons.
      self.__updateButtonIcons()

   def onPreviewCamera(self, btnOrIndex):
      """
      Called when we pass over or click a button and we want to preview the
      side of the cubemap that we are changing. This changes the camera
      orientation temporarily.
      """
      # Do nothing if we are editing a mercator background.
      if self.mMercator:
         return

      env = environ.Environment()

      if btnOrIndex is None:
         if self.mSavedCamera is not None:
            (fov, old_matrix) = self.mSavedCamera
            # Restore camera settings.
            env.manager.getCamera().setFov(fov)
            env.manager.navigator.setMatrix(old_matrix)
            env.updateGL()
            self.mSavedCamera = None
         return

      if type(btnOrIndex) == types.IntType:
         idx = btnOrIndex
         btn = self.mBtns[idx]
      else:
         btn = btnOrIndex
         idx = self.mBtns.index(btn)

      import osg2 as osg
      # Order from opendocument.vpresent: negx, posx, negy, posy, negz, posz.
      at_pnts = [osg.Pnt3f(-1.0, 0.0, 0.0), osg.Pnt3f( 1.0, 0.0, 0.0),
                 osg.Pnt3f( 0.0,-1.0, 0.0), osg.Pnt3f( 0.0, 1.0, 0.0),
                 osg.Pnt3f( 0.0, 0.0,-1.0), osg.Pnt3f( 0.0, 0.0, 1.0)]
      up_vecs = [osg.Vec3f( 0.0, 1.0, 0.0), osg.Vec3f( 0.0, 1.0, 0.0),
                 osg.Vec3f( 0.0, 0.0,-1.0), osg.Vec3f( 0.0, 0.0, 1.0),
                 osg.Vec3f( 0.0, 1.0, 0.0), osg.Vec3f( 0.0, 1.0, 0.0)]


      if self.mSavedCamera is None:
         # Save camera settings.
         self.mSavedCamera = (env.manager.camera.getFov(), osg.Matrix(env.manager.navigator.getMatrix()))

      # Update the navgator to look at correct wall.
      # NOTE: We do not need to do this because we are currently always
      #       viewing from the origin. In that case view_at becomes 2*at_pnt[idx]
      #       and we have the same viewing ray.
      #view_at = at_pnts[idx] + (at_pnts[idx] - from_pnt)
      #failed = osg.MatrixLookAt(new_matrix, at_pnts[idx], view_at, up_vecs[idx])
      from_pnt = osg.Pnt3f(0.0, 0.0, 0.0)
      new_matrix = osg.Matrix()
      failed = osg.MatrixLookAt(new_matrix, from_pnt, at_pnts[idx], up_vecs[idx])
      env.manager.navigator.setMatrix(new_matrix)

      # XXX: What should we do if we have an object centered on the origin.
      #      Should we hide all scene objects while we preview?

      import math
      env.manager.getCamera().setFov(math.radians(100.0))
      env.updateGL()

   def onPressed(self):
      """
      Called when the user first presses down on the button.
      NOTE: This happens before the toggled signal is emited.
      """
      if self.mCurrentDialog is not None:
         current_btn = self.sender()
         state = current_btn.isChecked()

         self.mCurrentDialog.accept()

         current_btn.blockSignals(True)
         current_btn.setChecked(state)
         current_btn.blockSignals(False)

   def onToggled(self, idx, checked=False):
      """ Called when the user clicks on one of the image tool buttons. """
      if checked:
         # Ensure that we have enough children.
         while len(self.mImageRefs) <= idx:
            self.mBackground.addChild(opendocument.createNode('vpresent:fill-image-ref'))
         # Get the correct image reference.
         img_ref = self.mImageRefs[idx]

         # Set current preview for the given wall.
         self.onPreviewCamera(idx)

         # Store the current undo index to revert changes later.
         self.mUndoIndex = self.undoStack.index()

         # Create a dialog to edit the image reference.
         self.mCurrentDialog = gui.common.imagerefedit.ImageRefEdit(img_ref, self.undoStack, parent=self)
         self.connect(self.mCurrentDialog, QtCore.SIGNAL("finished(int)"), self.onDialogFinished)
         img_ref._changeCallbacks.add(self.__onRefChanged)

         # Get the button that was clicked.
         btn = self.mBtns[idx]
         # Ensure that we do not cover up other buttons for the left and
         # front buttons 
         if btn in [self.mLeftBtn, self.mFrontBtn]:
            btn = self.mBottomBtn

         # Display the dialog at the lower right corner of the button.
         p = QtCore.QPoint(btn.x()+btn.width(), btn.y()+btn.height())
         p = self.mapToGlobal(p)
         self.mCurrentDialog.setGeometry(QtCore.QRect(p, QtCore.QSize(400,400)))
         self.mCurrentDialog.show()

   def onDialogFinished(self, result):
      # Remove our callback.
      img_ref = self.mCurrentDialog.mImageRef
      img_ref._changeCallbacks.remove(self.__onRefChanged)

      if QtGui.QDialog.Accepted != result:
         self.undoStack.setIndex(self.mUndoIndex)

      self.disconnect(self.mCurrentDialog, QtCore.SIGNAL("finished(int)"), self.onDialogFinished)
      self.mCurrentDialog = None

      # Clear all checked buttons.
      for b in self.mBtns:
         if b.isChecked():
            b.blockSignals(True)
            b.setChecked(False)
            b.blockSignals(False)
            break
      self.onPreviewCamera(None)


import vpresent_rc

if __name__ == "__main__":
   import sys
   app = QtGui.QApplication(sys.argv)
   dialog = SphereBackground()
   dialog.show()
   sys.exit(app.exec_())
